/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 abstract_tile.cpp

	$Header: /resource_editor/abstract_tile.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "abstract_tile.h"

#include <math.h>

#include "adventure_tile_size.h"
#include "player_color.h"

// -------------------------------------------------------------------
// Unnamed namespace
// -------------------------------------------------------------------

namespace
{

	// -------------------------------------------------------------------
	// states are: (never_explored,none), (not_visible,none),
	// (visible,none), (visible,basic), (visible,advanced),
	// (visible,expert), (visible,master), (visible,grandmaster)
	// -------------------------------------------------------------------
	int const k_visibility_states_per_team = 8;
	int const k_visibility_state_shift = 3;
	t_uint32 const k_visibility_state_mask = 0x7;

	// -------------------------------------------------------------------
	// -------------------------------------------------------------------
	inline int get_visibility_state( t_uint32 visibility_data, int team_num )
	{
		assert( team_num >= 0 && team_num < k_player_color_count );
		return ( visibility_data >> ( k_visibility_state_shift * team_num ) ) & k_visibility_state_mask;
	}

	// -------------------------------------------------------------------
	// -------------------------------------------------------------------
	struct t_out_of_range {};

	// -------------------------------------------------------------------
	// -------------------------------------------------------------------
	template < typename t_value, typename t_lower, typename t_upper >
	inline t_value validate_range( t_value value, t_lower lower, t_upper upper )
	{
		if ( value < lower || value >= upper )
			throw t_out_of_range();
		return value;
	}

	// -------------------------------------------------------------------
	// -------------------------------------------------------------------
	inline t_terrain_type read_terrain_type(
		std::streambuf &	stream,
		int					format_version )
	{
		t_terrain_type result;

		if ( format_version >= 2 )
			result = validate_range( t_terrain_type( get< t_uint8 >( stream ) ), 0, k_terrain_count );
		else
			result = translate( validate_range( t_old_terrain_type( get< t_uint8 >( stream ) ), 0, k_old_terrain_count ) );

		return result;
	}

	// -------------------------------------------------------------------
	// -------------------------------------------------------------------
	inline t_uint32 get_default_visibility_data()
	{
		return t_uint32( k_tile_visible ) << ( k_visibility_state_shift * k_player_gray );
	}

	// -------------------------------------------------------------------
	// -------------------------------------------------------------------
	t_uint32 read_visibility_data(
		std::streambuf &	stream,
		int					format_version )
	{
		if ( format_version < 3 )
			return get_default_visibility_data();

		if ( format_version >= 4 )
			return get< t_uint32 >( stream );

		t_uint32 result = get_default_visibility_data();

		t_uint16 old_data = get< t_uint16 >( stream );

		int shift=0;
		int i;
		for ( i = 0; i < k_active_player_color_count; ++i )
		{
			t_uint32 visibility = old_data & 0x3;
			result |= visibility << shift;

			old_data >>= 2;
			shift += k_visibility_state_shift;
		}

		return result;
	}

} // Unnamed namespace

// -------------------------------------------------------------------
// t_abstract_tile members
// -------------------------------------------------------------------

// -------------------------------------------------------------------
// -------------------------------------------------------------------
t_skill_mastery t_abstract_tile::get_anti_stealth( int team_num ) const
{
	if ( team_num < 0 )
		team_num = k_player_gray;
	int visibility_state = get_visibility_state( m_visibility_data, team_num );
	if ( visibility_state < k_tile_visible )
		return k_mastery_none;
	return t_skill_mastery( visibility_state - k_tile_visible + k_mastery_none );
}

// -------------------------------------------------------------------
// -------------------------------------------------------------------
t_tile_visibility t_abstract_tile::get_visibility( int team_num ) const
{
	if ( team_num < 0 )
		team_num = k_player_gray;
	int visibility_state = get_visibility_state( m_visibility_data, team_num );
	if ( visibility_state >= k_tile_visible )
		return k_tile_visible;
	return t_tile_visibility( visibility_state );
}

// -------------------------------------------------------------------
// -------------------------------------------------------------------
void t_abstract_tile::set_anti_stealth( int				team_num,
										t_skill_mastery	new_scouting )
{
	assert( new_scouting >= k_mastery_none && new_scouting < k_skill_mastery_count );
	if ( team_num < 0 )
		team_num = k_player_gray;
	assert( team_num < k_player_color_count );

	t_uint32 visibility_data = m_visibility_data;

	int visibility_state = get_visibility_state( m_visibility_data, team_num );
	assert( new_scouting == k_mastery_none || visibility_state >= k_tile_visible );

	if ( visibility_state < k_tile_visible )
		return;

	visibility_state = k_tile_visible + ( new_scouting - k_mastery_none );

	int shift = k_visibility_state_shift * team_num;
	visibility_data &= ~( k_visibility_state_mask << shift );
	visibility_data |= visibility_state << shift;

	m_visibility_data = visibility_data;
}

// -------------------------------------------------------------------
// -------------------------------------------------------------------
void t_abstract_tile::set_visibility(
	int					team_num,
	t_tile_visibility	new_visibility,
	t_skill_mastery		new_scouting )
{
	assert( new_visibility >= 0 && new_visibility < k_tile_visibility_count );
	assert( new_scouting >= k_mastery_none && new_scouting < k_skill_mastery_count );
	assert( new_scouting == k_mastery_none || new_visibility >= k_tile_visible );

	if ( team_num < 0 )
		team_num = k_player_gray;
	assert( team_num < k_player_color_count );

	t_uint32 visibility_data = m_visibility_data;

	int visibility_state = get_visibility_state( m_visibility_data, team_num );

	if ( new_visibility < k_tile_visible )
		visibility_state = new_visibility;
	else
		visibility_state = k_tile_visible + ( new_scouting - k_mastery_none );

	int shift = k_visibility_state_shift * team_num;
	visibility_data &= ~( k_visibility_state_mask << shift );
	visibility_data |= visibility_state << shift;

	m_visibility_data = visibility_data;
}

// -------------------------------------------------------------------
// -------------------------------------------------------------------
bool t_abstract_tile::write( std::streambuf & stream ) const
{
	try
	{
		put< t_uint8 >( stream, get_terrain() );
		put< t_uint8 >( stream, get_terrain_subtype() );
		put< t_uint8 >( stream, get_road_type() );
		put< t_uint8 >( stream, get_rock_terrain_type() );
		put< t_uint8 >( stream, has_rock_terrain_transition() );
		put< t_uint32 >( stream, m_visibility_data );

		int num_transitions = get_terrain_transition_count();
		put< t_uint8 >( stream, num_transitions );

		int i;
		for ( i = 0; i < num_transitions; ++i )
		{
			t_terrain_transition const & transition = get_terrain_transition( i );

			put< t_uint8 >( stream, transition.get_terrain() );
			put< t_uint8 >( stream, transition.get_terrain_subtype() );
			put< t_uint16 >( stream, transition.get_id() );
			put< t_uint8 >( stream, transition.get_mask_set() );
		}

		num_transitions = get_road_transition_count();
		put< t_uint8 >( stream, num_transitions );

		for ( i = 0; i < num_transitions; ++i )
		{
			t_road_transition const & transition = get_road_transition( i );

			put< t_uint8 >( stream, transition.get_road_type() );
			put< t_uint16 >( stream, transition.get_id() );
			put< t_uint8 >( stream, transition.get_mask_set() );
		}
	}
	catch ( t_streambuf_write_failure const & )
	{
		return false;
	}

	return true;
}

// -------------------------------------------------------------------
// -------------------------------------------------------------------
bool t_abstract_tile::read( std::streambuf & stream, int format_version )
{
	assert( format_version >= 0 && format_version <= k_current_format_version );

	t_terrain_type				new_terrain_type;
	int							new_terrain_subtype;
	t_road_type					new_road_type;
	t_rock_terrain_type			new_rock_terrain_type;
	bool						new_rock_terrain_transition;
	t_uint32					new_visibility_data;
	t_terrain_transition_vector	new_terrain_transition_vector;
	t_road_transition_vector	new_road_transition_vector;

	try
	{
		new_terrain_type = read_terrain_type( stream, format_version );
		new_terrain_subtype = validate_range( get< t_uint8 >( stream ), 0, get_terrain_subtype_count( new_terrain_type ) );
		new_road_type = validate_range( t_road_type( get< t_uint8 >( stream ) ), 0, k_road_type_count );

		if ( format_version >= 1 )
		{
			new_rock_terrain_type = validate_range( t_rock_terrain_type( get< t_uint8 >( stream ) ), 0, k_rock_terrain_type_count );
			new_rock_terrain_transition = get< t_uint8 >( stream ) != 0;
		}
		else
		{
			new_rock_terrain_type = k_rock_terrain_none;
			new_rock_terrain_transition = false;
		}

		new_visibility_data = read_visibility_data( stream, format_version );

		// Read in the terrain transitions
		int transition_count = get< t_uint8 >( stream );

		new_terrain_transition_vector.resize( transition_count );
		t_terrain_transition_vector::iterator terrain_iter = new_terrain_transition_vector.begin();

		while ( transition_count > 0 )
		{
			t_terrain_transition & transition = *terrain_iter;

			transition.set_terrain( read_terrain_type( stream, format_version ) );
			int subtype_count = get_terrain_subtype_count( transition.get_terrain() );
			transition.set_terrain_subtype( validate_range( get< t_uint8 >( stream ), 0, subtype_count ) );
			transition.set_id( get< t_uint16 >( stream ) );
			transition.set_mask_set( get< t_uint8 >( stream ) );

			++terrain_iter;
			--transition_count;
		}

		// Read in the road transitions
		transition_count = get< t_uint8 >( stream );

		new_road_transition_vector.resize( transition_count );
		t_road_transition_vector::iterator road_iter = new_road_transition_vector.begin();

		while ( transition_count > 0 )
		{
			t_road_transition & transition = *road_iter;

			transition.set_road_type( validate_range( t_road_type( get< t_uint8 >( stream ) ), 0, k_road_type_count ) );
			transition.set_id( get< t_uint16 >( stream ) );
			transition.set_mask_set( get< t_uint8 >( stream ) );

			++road_iter;
			--transition_count;
		}
	}
	catch ( t_out_of_range const & )
	{
		return false;
	}
	catch ( t_streambuf_read_failure const & )
	{
		return false;
	}

	swap_transition_vectors( new_terrain_transition_vector, new_road_transition_vector );
	set_terrain( new_terrain_type );
	set_terrain_subtype( new_terrain_subtype );
	set_road_type( new_road_type );
	set_rock_terrain_type( new_rock_terrain_type );
	set_rock_terrain_transition( new_rock_terrain_transition );
	m_visibility_data = new_visibility_data;

	return true;
}

// -------------------------------------------------------------------
// -------------------------------------------------------------------
t_uint32 t_abstract_tile::compute_default_visibility_data()
{
	return get_default_visibility_data();
}

// -------------------------------------------------------------------
// t_abstract_tile_vertex members
// -------------------------------------------------------------------

// -------------------------------------------------------------------
// -------------------------------------------------------------------
int t_abstract_tile_vertex::compute_default_light()
{
	return compute_edge_light( 0, 0 );
}

// -------------------------------------------------------------------
// Free functions
// -------------------------------------------------------------------

// -------------------------------------------------------------------
// -------------------------------------------------------------------
int compute_edge_light( int left_height, int right_height )
{
	int const k_ambient_light = k_max_tile_vertex_light / 6;

	static double const k_pi = 3.1415926535897932384626433832795;
	static double const k_half_pi = k_pi / 2;
	static double const k_pi_times_2 = k_pi * 2.0;
	static double const k_edge_length = k_adventure_tile_width / sqrt( 2.0 );

	double angle = atan2( left_height - right_height, k_edge_length ) + k_pi * 3.0 / 4.0;
	if ( angle > k_pi )
		angle = k_pi_times_2 - angle;

	angle -= k_half_pi;
	if ( angle <= 0 )
		return k_ambient_light;

	return k_ambient_light + int( angle * double( ( k_max_tile_vertex_light - k_ambient_light ) * 2 ) / k_half_pi );
}
